package net.gdface.utils;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import com.google.common.base.Objects;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.net.HostAndPort;

import jcifs.Address;
import jcifs.CIFSException;
import jcifs.NameServiceClient;
import jcifs.NetbiosAddress;
import jcifs.context.SingletonContext;
import jcifs.netbios.NbtAddress;
import jcifs.netbios.UniAddress;

/**
 * 基于jcifs库的名字解析工具<br>
 * 使用该类的需要添加额外的依赖库:<br>
 * {@code  'eu.agno3.jcifs:jcifs-ng:2.1.2'}
 * @author guyadong
 *
 */
public class JcifsUtil {
	private final static NameServiceClient nsc ;
	static{
		Properties props = new Properties();
		// 响应数据接收超时设置
		props.setProperty("jcifs.netbios.retryTimeout", "2000");
		try {
			SingletonContext.init(props);
		} catch (CIFSException e) {
			e.printStackTrace();
		}
		nsc = SingletonContext.getInstance().getNameServiceClient();
	}
	/**
	 * 检查指定地址{@link Address}的主机是否有效的线程
	 * @author guyadong
	 *
	 */
	static class CheckHostStatusThread extends Thread{
		final Address address;
		final AtomicReference<String> output;
		private final List<Thread> threads;
		public CheckHostStatusThread(Address address, AtomicReference<String> output, List<Thread> threads) {
			this.address = address;
			this.output = output;
			this.threads = threads;
			setName(this.getClass().getSimpleName() + "-thread");
			this.threads.add(this);
		}
		@Override
		public void run() {
			try {
				String ip = address.getHostAddress();
				if(address instanceof NetbiosAddress){
					nsc.getNodeStatus(address.unwrap(NbtAddress.class));
				}else if(address instanceof UniAddress){
					Object obj = ((UniAddress)address).getAddress();
					if(obj instanceof NetbiosAddress){
						nsc.getNodeStatus((NetbiosAddress)obj);
					}else if( obj instanceof InetAddress){
						nsc.getByName(ip);
					}else{
						throw new IllegalArgumentException("INVALID ADDRESS Type " + address.getClass().getName());
					}
				}else{
					throw new IllegalArgumentException("INVALID ADDRESS Type " + address.getClass().getName());
				}
				// 保存第一个执行结束的线程的结果
				if(output.compareAndSet(null, ip)){
					synchronized (output) {						
						output.notifyAll();
					}
				}
			} catch (UnknownHostException e) {
				// DO NOTHING
			}finally{
				this.threads.remove(this);
			}
		}
		
	}
	/**
	 * 
	 * 并发检查输入的地址是否有效，返回第一个有效的地址 
	 * @param addrs
	 * @return 返回IP地址,如果所有地址都无效返回{@code null}
	 */
	private static String firstValidAddress(Address... addrs){
		AtomicReference<String> addr = new AtomicReference<String>();
		List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>(addrs.length));
		for(Address address : addrs){
			// 为每个地址创建一个线程检检查主机地址是否有效
			if(IPAddressUtil.isIPv4LiteralAddress(address.getHostAddress())){
				new CheckHostStatusThread(address,addr, threads).start();	
			}			
		}
		// 等待第一个返回的有效主机地址 
		synchronized (addr) {
			while(addr.get() == null && !threads.isEmpty()){
				try {
					addr.wait(100);
				} catch (InterruptedException e) {
					break;
				}
			}
		}
		return addr.get();
	}
	/**
	 * 
	 * 并发检查输入的地址是否有效，返回第一个有效的地址 
	 * @param addrs
	 * @return 返回IP地址
	 * @throws UnknownHostException 所有地址都无效
	 */
	private static String firstValidAddressChecked(Address... addrs) throws UnknownHostException{
		String validAddress = firstValidAddress(addrs);
		if(validAddress == null){
			throw new UnknownHostException("NOT FOUND REACHABLE HOST ");
		}
		return validAddress;
	}

	/**
	 * 根据{@code host}提供的局域网主机名返回对应的IP地址<br>
	 * 用于局域网内主机名解析为IP地址,<br>
	 * 如果{@code host}为{@code null}或为IP地址则返回输入的{@code host}
	 * 如果{@code host}不是有效的局域网主机名则抛出{@link UnknownHostException}<br>
	 * @param host 主机地址
	 * @return host address like "192.168.1.10"
	 * @throws UnknownHostException 无法解析主机名
	 * @see NameServiceClient#getNbtAllByName(String, int, String, InetAddress)
	 */
	public static String addressOfLanhost(String host) throws UnknownHostException{
		//return nsc.getByName(host).getHostAddress();
		if(host == null || host.matches(REG_IPV4)){
			return host;
		}
		NetbiosAddress[] addrs = nsc.getNbtAllByName(host,0x00,null,null);
		return firstValidAddressChecked(addrs);
	}
	
	/**
	 * 根据{@code host}提供的主机名返回IP地址<br>
	 * 用于局域网内主机名解析为IP地址,<br>
	 * 如果解析失败则返回输入的{@code host},<br>
	 * 如果{@code host}为{@code null}或为IP地址则返回输入的{@code host}
	 * @param host 主机地址
	 * @return host address like "192.168.1.10"
	 * @throws UnknownHostException 无法解析主机名
	 * @see NameServiceClient#getByName(String)
	 */
	public static String hostAddressOf(String host) throws UnknownHostException{
		if(host == null || host.matches(REG_IPV4)){
			return host;
		}
		// 提供的主机名返回所有绑定的地址对象
		Address[] addrs = nsc.getAllByName(host, true);
		String validAddress = firstValidAddress(addrs);
		if(validAddress == null){
			validAddress = addressOfLanhost(host);
		}
		return validAddress;
	}
	/**
	 * 根据{@code host}提供的主机名返回IP地址<br>
	 * 解析失败返回{@code null}
	 * @param host address like "myhost"
	 * @return IP地址
	 */
	public static String getAddressIfPossible(String host){
		try {
			return hostAddressOf(host);
		} catch (UnknownHostException e) {
			return null;
		}
	}
	/**
	 * 判断{@code host}是否为可解析的主机名
	 * @param host
	 * @return 如果是可解析的主机名返回{@code true},否则返回{@code false}
	 */
	public static boolean isResolvableHost(String host){
		return getAddressIfPossible(host) != null;
	}
	private static final String REG_IPV4 = "^((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))$";
	private static final LoadingCache<String, String> IP_CACHE = CacheBuilder.newBuilder()
			.expireAfterWrite(10, TimeUnit.MINUTES)/** 数据有效期 */
			.build(new CacheLoader<String, String>(){

		@Override
		public String load(String host) throws Exception {
			try {
				String address = hostAddressOf(host);
				// 非局域网域名保持原样
				return IPAddressUtil.internalIp(address) ? address : host;
			} catch (UnknownHostException e) {
				return host;
			}	
		}});

	/**
	 * 如果input中的主机名为局域网主机名则替换为IP地址，否则返回input
	 * @param input
	 * @return URI
	 */
	public static URI changeHostIfLanhost(URI input){
		if(input != null && input.getHost() != null && !input.getHost().matches(REG_IPV4)){
			// 如果host不是IP地址格式，则替换主机名为对应的IP地址
			String host = IP_CACHE.getUnchecked(input.getHost());
			URI u2 = changeHostUnchecked(input,host);
			return u2;
		}
		return input;
	}
	/**
	 * 如果input中的主机名为局域网主机名则替换为IP地址，否则返回input,
	 * input 为URI格式(such as '//hostname:port','//hostname:port','http://domainname:port')或 'host:port','host'格式
	 * @param input
	 * @return URI string
	 */
	public static String changeHostIfLanhost(String input){
		if(input != null){
			try {
				URI uri = new URI(input);
				if(uri.getHost() != null){
					return changeHostIfLanhost(uri).toString();
				}
			} catch (URISyntaxException e1) {
				// DO NOTHING
			}
			
			try {
				HostAndPort hostAndPort = HostAndPort.fromString(input);
				String changed = changeHostIfLanhost(URI.create("//" + hostAndPort.toString())).toString();
				return changed.substring(2);	
			} catch (Exception e) {
				return changeHostIfLanhost(URI.create(input)).toString();	
			}
			
		}
		return input;
	}
	/**
	 * 如果input中的主机名为局域网主机名则替换为IP地址，否则返回input
	 * @param input
	 * @return URL string
	 */
	public static URL changeHostIfLanhost(URL input){
		if(input != null){
			try {
				return changeHostIfLanhost(input.toURI()).toURL();
			} catch (MalformedURLException | URISyntaxException e) {
				throw new RuntimeException(e);
			}
		}
		return input;
	}
	private static URI changeHost(URI bindAddr, String host) throws URISyntaxException {
		if(Objects.equal(bindAddr.getHost(),host)){
			return bindAddr;
		}
        return new URI(bindAddr.getScheme(), bindAddr.getUserInfo(), host, bindAddr.getPort(), bindAddr
            .getPath(), bindAddr.getQuery(), bindAddr.getFragment());
    }
    private static URI changeHostUnchecked(URI bindAddr, String host) {
        try {
			return changeHost(bindAddr,host);
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
    }
}
